﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Web;
using MetaCollection.ContentService.Configuration;
using MetaCollection.ContentService.Http;
using MetaCollection.ContentService.WebDav;
using MetaCollection.Framework;
using System.Globalization;
//using MetaCollection.ContentService.WebDav.Methods;
using System.Xml.Linq;
using System.Xml;
using Model=MetaCollection.Framework.Model;
using System.IO;

namespace MetaCollection.ContentService
{
	public class ReportHandler : IHttpHandler
	{
		//private ContentEndpoint m_ContentEndpoint;
		private string m_CollectionRootName;
		private IRequestHandler m_RequestHandler;

		public ReportHandler()
		{
			m_RequestHandler = RequestHandlerFactory.CreateRequestHandler();
			m_CollectionRootName = ((ContentConfigurationSection)ConfigurationManager.GetSection("contentConfiguration")).RootName;
			//m_ContentEndpoint = new ContentEndpoint(m_RequestHandler, HttpMethods.Get);
		}

		public bool IsReusable
		{
			get { return true; }
		}

		public void ProcessRequest(HttpContext httpContext)
		{
			var context = new DavContext(httpContext);
			int requestedDepth;
			try
			{
				requestedDepth = Convert.ToInt16(httpContext.Request.QueryString["depth"]);
			}
			catch (FormatException)
			{
				requestedDepth = 0;
			}
			catch (OverflowException)
			{
				requestedDepth = 0;
			}

			string reportUriSegment = httpContext.Request.QueryString["uri"];
			Uri reportUri = new Uri(String.Format(CultureInfo.InvariantCulture, "col://{0}/{1}", m_CollectionRootName, reportUriSegment.TrimStart('/')));
			Model.IItemInstance requestedItem = m_RequestHandler.ItemRequest(context.User, RequestTypes.GetItem, reportUri, Model.LoadOptions.ItemComplete).FirstOrDefault();
			if (requestedItem != null)
			{
				XElement requestedProperties = XElement.Parse("<?xml version=\"1.0\"?>\n<D:propfind xmlns:D=\"DAV:\" xmlns:mc=\"uri://MetaCollection/v2\">\n<D:allprop/>\n</D:propfind>");

				httpContext.Response.ContentType = "text/xml";
				httpContext.Response.StatusCode = (int)Http.SuccessStatus.Ok;
				httpContext.Response.Headers.Add("Content-Disposition", String.Format(CultureInfo.InvariantCulture, "attachment;filename={0}-{1}-report.xml", reportUriSegment.TrimStart('/').Replace('/', '-'), requestedDepth));
				httpContext.Response.Cache.SetCacheability(HttpCacheability.ServerAndPrivate);
				httpContext.Response.Cache.SetExpires(DateTime.Now.AddMinutes(5)); // reduce the server load for back to back requests.
				httpContext.Response.Buffer = false;
				IEnumerable<XElement> response = BuildResponse(context, m_RequestHandler, requestedItem, requestedProperties, requestedDepth);

				OutputXml(response, httpContext.Response.Output);
			}
		}

		public IEnumerable<XElement> BuildResponse(DavContext context, IRequestHandler handler, Model.IItemInstance item, XElement requestedPropertiesXml, int depth)
		{
			var requestedProperties = Properties.RequestedProperties(requestedPropertiesXml);
			IEnumerable<XElement> responseData = item.DavData(Model.LoadOptions.ItemComplete).PropInfoResponse(context, requestedProperties);
			IEnumerable<Model.IItemInstance> items = item.AggregateToDepth(context, handler, depth);
			if (items == null)
				return responseData;
			else 
				return responseData.Concat(items.DavData(Model.LoadOptions.ItemComplete).PropInfoResponse(context, requestedProperties));
		}

		/// <summary>
		/// Outputs XML as the response body of a multi-status WEBDAV response with the correct mimetype and status code.
		/// </summary>
		/// <param name="data">The xml data to output in the response.</param>
		private void OutputXml(IEnumerable<XElement> data, TextWriter outputWriter)
		{
			// TODO: This method is a modified copy of the method in DavResponse. Investigate DRYing it up.
			XStreamingElement stream = new XStreamingElement(XName.Get("multistatus", "DAV:"), DavResponse.Namespaces, data);
			XmlWriterSettings settings = new XmlWriterSettings
			{
#if DEBUG
				Indent = true,
				IndentChars = "\t"
#endif
			};

			using (XmlWriter writer = XmlWriter.Create(outputWriter, settings))
			{
				stream.WriteTo(writer);
				writer.Flush();
			}
		}
	}

	public static partial class Extensions
	{
		/// <summary>
		/// Returns a collection of children of the specified item with a maximum depth in the tree.
		/// </summary>
		/// <param name="item">item to find children for</param>
		/// <param name="handler">handler to use for making item requests</param>
		/// <param name="depth">depth of tree under item to include</param>
		/// <returns>a collection of items satisfying the depth constraint that have a common ancestor of item.</returns>
		public static IEnumerable<Model.IItemInstance> AggregateToDepth(this Model.IItemInstance item, DavContext context, IRequestHandler handler, int depth)
		{
			IEnumerable<Model.IItemInstance> items;

			if (handler == null)
				throw new ArgumentNullException("handler");
			if (item == null || item.Href == null)
				throw new ArgumentNullException("item", "Item and Item.Href can not be null.");

			if (depth == 1)
				items = handler.ItemRequest(context.User, RequestTypes.GetItem | RequestTypes.ProcessDirectDescendents, item.Href, Model.LoadOptions.ItemComplete);
			else if (depth > 1)
				items = handler.ItemRequest(context.User, RequestTypes.GetItem | RequestTypes.ProcessAllDescendents, item.Href, Model.LoadOptions.ItemComplete);
			else
				yield break;

			int baseDepth = item.Href.Segments.Length;
			int maxDepth = baseDepth + depth;

			foreach (var childItem in items)
			{
				// if the child is null, not really a child of this item or a child deeper in the tree than asked for; skip.
				if (childItem.Href == null || (childItem.Href.Segments.Length < baseDepth || childItem.Href.Segments.Length > maxDepth))
					continue;
				yield return childItem;
			}
		}
	}
}
